package com.nbilyk.ntween {
	import com.nbilyk.ntween.events.NTweenEvent;
	import com.nbilyk.utils.DeferredFunctionCall;
	
	import fl.motion.easing.Linear;
	
	import flash.events.EventDispatcher;

	[Event(name="tweenEnd", type="com.nbilyk.ntween.events.NTweenEvent")]
	[Event(name="tweenStart", type="com.nbilyk.ntween.events.NTweenEvent")]
	[Event(name="tweenUpdate", type="com.nbilyk.ntween.events.NTweenEvent")]
	public class NTween extends EventDispatcher implements ITimeUpdatable {
		
		/**
		 * The default NTween class to use when using the static NTween.tween method.
		 * This class must extend NTween.
		 * 
		 * @default com.nbilyk.ntween.NTween
		 */
		public static var defaultTweenClass:Class = NTween;
		
		/**
		 * The default ease is a linear ease.
		 */
		public static var defaultEase:Function = Linear.easeNone;
		
		/**
		 * The target Object of which to tween.
		 */
		public var target:Object;
		
		/**
		 * An object of name / value pairs with which to tween the given target.
		 * e.g.  { x: 100, y: 200 } will tween the target's x and y properties to 100,200.
		 * If the target does not have a property of that name, this class will throw an error.
		 */
		public var props:Object;
		
		/**
		 * This is automatically set on registration of the tween.
		 */
		public var initialValues:Object;

		/**
	     *  The easing function method should accept the following arguments:
	     *  @param t Specifies time.
	     *  @param b Specifies the initial position of a component.
	     *  @param c Specifies the total change in position of the component.
	     *  @param d Specifies the duration of the effect, in milliseconds.
	     *  @return Number corresponding to the position of the component.
	     */
		public var ease:Function;
		
		/**
		 * The delay, in milliseconds, before the tween begins.
		 */
		public var delay:int;
		
		/**
		 * The duration of the tween, in milliseconds.
		 */
		public var duration:int;
		
		
		public var hasBegun:Boolean;

		public function NTween() {
			super();
		}
		
		/**
		 * @var targetVal - The target object to tween.
		 * @var propsVal - An object representing the variables to Tween and their ending values. E.g. { x: 20, y: 30 }
		 */
		public function init(targetVal:Object, propsVal:Object, durationVal:int = 1000, delayVal:int = 0, easeVal:Function = null):void {
			target = targetVal;
			props = propsVal;
			duration = durationVal;
			delay = delayVal;
			ease = (easeVal == null) ? defaultEase : easeVal;
			initialValues = {};
			for (var all:String in props) {
				if (!target.hasOwnProperty(all)) {
					throw new Error("target does not have the property: " + all);
				}
				if (target[all] is Number) {
					initialValues[all] = target[all];
				}
			}
			NTweenModifier.defaultInitTweenMethod(this);
			NTimeEngine.instance.registerObject(this);
		}

		/**
		 * Called from the time engine, sets the playhead time to [value].
		 */
		public function setCurrentTime(value:int):void {
			if (value < delay) return;
			if (!hasBegun) {
				dispatchEvent(new NTweenEvent(NTweenEvent.TWEEN_START));
				hasBegun = true;
			}
			dispatchEvent(new NTweenEvent(NTweenEvent.TWEEN_UPDATE));
			for (var all:String in initialValues) {
				var newValue:Number = ease(value - delay, initialValues[all], props[all] - initialValues[all], duration);
				target[all] = newValue;
			}

			if (value >= duration + delay) {
				endTween();
			}
		}

		/**
		 * Unregisters a tween so it will stop playing.
		 */
		public function removeTween():void {
			NTimeEngine.instance.unregisterObject(this);
		}

		/**
		 * Jumps a tween to the end.
		 */
		public function endTween():void {
			for (var all:String in initialValues) {
				target[all] = props[all];
			}
			NTimeEngine.instance.unregisterObject(this);
			dispatchEvent(new NTweenEvent(NTweenEvent.TWEEN_END));
		}
		
		//--------------------------------------------------
		// Static helper methods
		//--------------------------------------------------

		/**
		 * A utility method to create a new NTween object and register certain event listeners to the given deferred function calls.
		 */
		public static function tween(targetVal:Object, propsVal:Object, durationVal:int = 1000, delayVal:int = 0, easeVal:Function = null, onCompleteCall:DeferredFunctionCall = null, onUpdateCall:DeferredFunctionCall = null, onStartCall:DeferredFunctionCall = null):NTween {
			var tween:NTween = new defaultTweenClass();
			tween.init(targetVal, propsVal, durationVal, delayVal, easeVal);
			if (onCompleteCall) tween.addEventListener(NTweenEvent.TWEEN_END, onCompleteCall.call);
			if (onUpdateCall) tween.addEventListener(NTweenEvent.TWEEN_UPDATE, onUpdateCall.call);
			if (onStartCall) tween.addEventListener(NTweenEvent.TWEEN_START, onStartCall.call);
			return tween;
		}
		
		/**
		 * Removes all NTween objects from the time engine.
		 */
		public static function removeAllTweens():void {
			var objs:Vector.<ITimeUpdatable> = NTimeEngine.instance.registeredObjects;
			var n:uint = objs.length;
			for (var i:uint = 0; i < n; i++) {
				if (objs[i] is NTween) {
					objs.splice(i, 1);
					i--; n--;
				}
			}
		}

		/**
		 * Removes all tweens that have the target [target]
		 */
		public static function removeTweens(target:Object):void {
			var objs:Vector.<ITimeUpdatable> = NTimeEngine.instance.registeredObjects; /* Type ITimeUpdatable */
			var n:uint = objs.length;
			for (var i:uint = 0; i < n; i++) {
				if (objs[i] is NTween && NTween(objs[i]).target == target) {
					objs.splice(i, 1);
					i--; n--;
				}
			}
		}

		/**
		 * Counts the number of tweens with a given [target].
		 */
		public static function getTweenCount(target:Object):uint {
			var c:uint = 0;
			for each (var obj:ITimeUpdatable in NTimeEngine.instance.registeredObjects) {
				if (obj is NTween && NTween(obj).target == target) c++;
			}
			return c;
		}

		/**
		 * Returns true if there are any tweens with a given [target].
		 */
		public static function getHasTween(target:Object):Boolean {
			for each (var obj:ITimeUpdatable in NTimeEngine.instance.registeredObjects) {
				if (obj is NTween && NTween(obj).target == target) return true;
			}
			return false;
		}

		/**
		 * Returns an array of tweens for the given target.
		 */
		public static function getTweensForTarget(target:Object):Vector.<NTween> {
			var ret:Vector.<NTween> = new Vector.<NTween>(); // Type NTween
			for each (var obj:ITimeUpdatable in NTimeEngine.instance.registeredObjects) {
				if (obj is NTween) {
					var iTween:NTween = NTween(obj);
					if (iTween.target == target) {
						ret.push(iTween);
					}
				}
			}
			return ret;
		}
	}
}